/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.common.misc.el;

import javax.el.*;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * EL 表达式 Engine
 * <p/>
 * Copy from Dozer
 */
public class ELEngine {
    protected final ExpressionFactory factory;
    protected final CompositeELResolver resolver;
    protected ELContext context;

    public ELEngine() {
        factory = ExpressionFactory.newInstance();

        resolver = new CompositeELResolver();
        resolver.add(new ArrayELResolver());
        resolver.add(new ListELResolver());
        resolver.add(new MapELResolver());
        resolver.add(new BeanELResolver());
        context = new SimpleContext(resolver);
    }

    public <T> void setVariable(String key, T value) {
        setVariable(key, value, value.getClass());
    }

    public <T> void setVariable(String key, T value, Class<? extends T> type) {
        VariableMapper variableMapper = context.getVariableMapper();
        ValueExpression valueExpression = factory.createValueExpression(value, type);
        variableMapper.setVariable(key, valueExpression);
    }

    public void setFunction(String prefix, Method method) {
        Functions functions = (Functions) context.getFunctionMapper();
        functions.setFunction(prefix, method.getName(), method);
    }

    public void setFunction(String prefix, String name, Method method) {
        Functions functions = (Functions) context.getFunctionMapper();
        functions.setFunction(prefix, name, method);
    }

    /**
     * 解析 EL 表达式 eg:
     * "abc" --> "abc"
     * "${abc}" --> Object
     * "map.key: ${map.key} ${map.value}" --> "map.key key value"
     *
     * @param expression
     * @return
     */
    public String resolve(String expression) {
        return resolve(expression, String.class);
    }

    @SuppressWarnings("unchecked")
    public <T> T resolve(String expression, Class<T> type) {
        ValueExpression expr = factory.createValueExpression(context, expression, type);
        return (T) expr.getValue(context);
    }

    static class SimpleContext extends ELContext {

        private Functions functions = new Functions();
        private Variables variables = new Variables();
        private ELResolver resolver;

        SimpleContext(ELResolver resolver) {
            this.resolver = resolver;
        }

        public ELResolver getELResolver() {
            return resolver;
        }

        @Override
        public FunctionMapper getFunctionMapper() {
            return functions;
        }

        @Override
        public VariableMapper getVariableMapper() {
            return variables;
        }
    }

    static class Functions extends FunctionMapper {
        final Map<String, Method> map = new HashMap<String, Method>();

        @Override
        public Method resolveFunction(String prefix, String localName) {
            return map.get(prefix + ":" + localName);
        }

        public void setFunction(String prefix, String localName, Method method) {
            map.put(prefix + ":" + localName, method);
        }
    }

    static class Variables extends VariableMapper {
        final Map<String, ValueExpression> map = new HashMap<String, ValueExpression>();

        @Override
        public ValueExpression resolveVariable(String variable) {
            return map.get(variable);
        }

        @Override
        public ValueExpression setVariable(String variable, ValueExpression expression) {
            return map.put(variable, expression);
        }
    }
}
